use cpal::traits::{DeviceTrait, HostTrait};
use cpal::{Device, Host, SampleFormat};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AudioDeviceError {
    #[error("Device enumeration failed: {0}")]
    EnumerationFailed(String),
    #[error("Device not found: {0}")]
    DeviceNotFound(String),
    #[error("Stream creation failed: {0}")]
    StreamCreationFailed(String),
    #[error("Stream configuration error: {0}")]
    StreamConfigError(String),
    #[error("Audio format not supported: {0}")]
    UnsupportedFormat(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioDeviceInfo {
    pub id: String,
    pub name: String,
    pub is_default: bool,
    pub supported_sample_rates: Vec<u32>,
    pub supported_channels: Vec<u16>,
    pub supported_formats: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioStreamConfig {
    pub sample_rate: u32,
    pub channels: u16,
    pub buffer_size: u32,
    pub format: String,
}

impl Default for AudioStreamConfig {
    fn default() -> Self {
        Self {
            sample_rate: 48000,
            channels: 1,
            buffer_size: 1024,
            format: "f32".to_string(),
        }
    }
}

pub struct AudioDeviceManager {
    host: Host,
    input_devices: Arc<RwLock<Vec<AudioDeviceInfo>>>,
    output_devices: Arc<RwLock<Vec<AudioDeviceInfo>>>,
    current_input_device_id: Arc<RwLock<Option<String>>>,
    current_output_device_id: Arc<RwLock<Option<String>>>,
}

impl AudioDeviceManager {
    pub fn new() -> Result<Self, AudioDeviceError> {
        let host = cpal::default_host();
        
        Ok(Self {
            host,
            input_devices: Arc::new(RwLock::new(Vec::new())),
            output_devices: Arc::new(RwLock::new(Vec::new())),
            current_input_device_id: Arc::new(RwLock::new(None)),
            current_output_device_id: Arc::new(RwLock::new(None)),
        })
    }

    pub async fn enumerate_devices(&self) -> Result<(), AudioDeviceError> {
        let mut input_devices = Vec::new();
        let mut output_devices = Vec::new();

        // Get default devices
        let default_input = self.host.default_input_device();
        let default_output = self.host.default_output_device();

        // Enumerate input devices
        let input_devices_iter = self.host.input_devices()
            .map_err(|e| AudioDeviceError::EnumerationFailed(e.to_string()))?;

        for (index, device) in input_devices_iter.enumerate() {
            let device_info = self.get_device_info(&device, &format!("input_{}", index), 
                default_input.as_ref().map(|d| d.name().unwrap_or_default() == device.name().unwrap_or_default()).unwrap_or(false))?;
            input_devices.push(device_info);
        }

        // Enumerate output devices
        let output_devices_iter = self.host.output_devices()
            .map_err(|e| AudioDeviceError::EnumerationFailed(e.to_string()))?;

        for (index, device) in output_devices_iter.enumerate() {
            let device_info = self.get_device_info(&device, &format!("output_{}", index),
                default_output.as_ref().map(|d| d.name().unwrap_or_default() == device.name().unwrap_or_default()).unwrap_or(false))?;
            output_devices.push(device_info);
        }

        *self.input_devices.write().await = input_devices;
        *self.output_devices.write().await = output_devices;

        Ok(())
    }

    fn get_device_info(&self, device: &Device, id: &str, is_default: bool) -> Result<AudioDeviceInfo, AudioDeviceError> {
        let name = device.name()
            .map_err(|e| AudioDeviceError::EnumerationFailed(e.to_string()))?;

        // Get supported configurations
        let mut supported_sample_rates = Vec::new();
        let mut supported_channels = Vec::new();
        let mut supported_formats = Vec::new();

        // Try to get supported input configurations
        if let Ok(configs) = device.supported_input_configs() {
            for config in configs {
                supported_sample_rates.push(config.min_sample_rate().0);
                supported_sample_rates.push(config.max_sample_rate().0);
                supported_channels.push(config.channels());
                
                match config.sample_format() {
                    SampleFormat::I8 => supported_formats.push("i8".to_string()),
                    SampleFormat::I16 => supported_formats.push("i16".to_string()),
                    SampleFormat::I32 => supported_formats.push("i32".to_string()),
                    SampleFormat::I64 => supported_formats.push("i64".to_string()),
                    SampleFormat::U8 => supported_formats.push("u8".to_string()),
                    SampleFormat::U16 => supported_formats.push("u16".to_string()),
                    SampleFormat::U32 => supported_formats.push("u32".to_string()),
                    SampleFormat::U64 => supported_formats.push("u64".to_string()),
                    SampleFormat::F32 => supported_formats.push("f32".to_string()),
                    SampleFormat::F64 => supported_formats.push("f64".to_string()),
                    _ => {}
                }
            }
        }

        // Try to get supported output configurations if input failed
        if supported_sample_rates.is_empty() {
            if let Ok(configs) = device.supported_output_configs() {
                for config in configs {
                    supported_sample_rates.push(config.min_sample_rate().0);
                    supported_sample_rates.push(config.max_sample_rate().0);
                    supported_channels.push(config.channels());
                    
                    match config.sample_format() {
                        SampleFormat::I8 => supported_formats.push("i8".to_string()),
                        SampleFormat::I16 => supported_formats.push("i16".to_string()),
                        SampleFormat::I32 => supported_formats.push("i32".to_string()),
                        SampleFormat::I64 => supported_formats.push("i64".to_string()),
                        SampleFormat::U8 => supported_formats.push("u8".to_string()),
                        SampleFormat::U16 => supported_formats.push("u16".to_string()),
                        SampleFormat::U32 => supported_formats.push("u32".to_string()),
                        SampleFormat::U64 => supported_formats.push("u64".to_string()),
                        SampleFormat::F32 => supported_formats.push("f32".to_string()),
                        SampleFormat::F64 => supported_formats.push("f64".to_string()),
                        _ => {}
                    }
                }
            }
        }

        // Remove duplicates and sort
        supported_sample_rates.sort_unstable();
        supported_sample_rates.dedup();
        supported_channels.sort_unstable();
        supported_channels.dedup();
        supported_formats.sort_unstable();
        supported_formats.dedup();

        // Provide defaults if nothing was found
        if supported_sample_rates.is_empty() {
            supported_sample_rates = vec![44100, 48000];
        }
        if supported_channels.is_empty() {
            supported_channels = vec![1, 2];
        }
        if supported_formats.is_empty() {
            supported_formats = vec!["f32".to_string()];
        }

        Ok(AudioDeviceInfo {
            id: id.to_string(),
            name,
            is_default,
            supported_sample_rates,
            supported_channels,
            supported_formats,
        })
    }

    pub async fn get_input_devices(&self) -> Vec<AudioDeviceInfo> {
        self.input_devices.read().await.clone()
    }

    pub async fn get_output_devices(&self) -> Vec<AudioDeviceInfo> {
        self.output_devices.read().await.clone()
    }

    pub async fn set_input_device(&self, device_id: &str) -> Result<(), AudioDeviceError> {
        // Validate that the device exists
        self.find_input_device_by_id(device_id).await?;
        *self.current_input_device_id.write().await = Some(device_id.to_string());
        log::info!("Set input device to: {}", device_id);
        Ok(())
    }

    pub async fn set_output_device(&self, device_id: &str) -> Result<(), AudioDeviceError> {
        // Validate that the device exists
        self.find_output_device_by_id(device_id).await?;
        *self.current_output_device_id.write().await = Some(device_id.to_string());
        log::info!("Set output device to: {}", device_id);
        Ok(())
    }

    async fn find_input_device_by_id(&self, device_id: &str) -> Result<Device, AudioDeviceError> {
        let input_devices_iter = self.host.input_devices()
            .map_err(|e| AudioDeviceError::EnumerationFailed(e.to_string()))?;

        for (index, device) in input_devices_iter.enumerate() {
            if format!("input_{}", index) == device_id {
                return Ok(device);
            }
        }

        Err(AudioDeviceError::DeviceNotFound(device_id.to_string()))
    }

    async fn find_output_device_by_id(&self, device_id: &str) -> Result<Device, AudioDeviceError> {
        let output_devices_iter = self.host.output_devices()
            .map_err(|e| AudioDeviceError::EnumerationFailed(e.to_string()))?;

        for (index, device) in output_devices_iter.enumerate() {
            if format!("output_{}", index) == device_id {
                return Ok(device);
            }
        }

        Err(AudioDeviceError::DeviceNotFound(device_id.to_string()))
    }

    pub async fn create_input_stream(&self, _config: AudioStreamConfig) -> Result<(), AudioDeviceError> {
        let device_id = self.current_input_device_id.read().await;
        let device_id = device_id.as_ref()
            .ok_or_else(|| AudioDeviceError::DeviceNotFound("No input device selected".to_string()))?;

        // For now, just validate that the device exists and log
        self.find_input_device_by_id(device_id).await?;
        log::info!("Would create input stream for device: {}", device_id);
        
        // TODO: Implement actual stream creation when integrating with RTC library
        // The streams will be managed by the RTC library for actual audio processing
        Ok(())
    }

    pub async fn create_output_stream(&self, _config: AudioStreamConfig) -> Result<(), AudioDeviceError> {
        let device_id = self.current_output_device_id.read().await;
        let device_id = device_id.as_ref()
            .ok_or_else(|| AudioDeviceError::DeviceNotFound("No output device selected".to_string()))?;

        // For now, just validate that the device exists and log
        self.find_output_device_by_id(device_id).await?;
        log::info!("Would create output stream for device: {}", device_id);
        
        // TODO: Implement actual stream creation when integrating with RTC library
        // The streams will be managed by the RTC library for actual audio processing
        Ok(())
    }

    pub async fn stop_input_stream(&self) -> Result<(), AudioDeviceError> {
        log::info!("Would stop input stream");
        // TODO: Implement actual stream stopping when integrating with RTC library
        Ok(())
    }

    pub async fn stop_output_stream(&self) -> Result<(), AudioDeviceError> {
        log::info!("Would stop output stream");
        // TODO: Implement actual stream stopping when integrating with RTC library
        Ok(())
    }

    pub async fn stop_all_streams(&self) -> Result<(), AudioDeviceError> {
        self.stop_input_stream().await?;
        self.stop_output_stream().await?;
        Ok(())
    }

    pub async fn get_current_input_device_id(&self) -> Option<String> {
        self.current_input_device_id.read().await.clone()
    }

    pub async fn get_current_output_device_id(&self) -> Option<String> {
        self.current_output_device_id.read().await.clone()
    }

    pub fn get_default_input_device(&self) -> Option<Device> {
        self.host.default_input_device()
    }

    pub fn get_default_output_device(&self) -> Option<Device> {
        self.host.default_output_device()
    }

    pub async fn get_current_input_device(&self) -> Option<Device> {
        let device_id = self.current_input_device_id.read().await;
        if let Some(id) = device_id.as_ref() {
            self.find_input_device_by_id(id).await.ok()
        } else {
            self.get_default_input_device()
        }
    }

    pub async fn get_current_output_device(&self) -> Option<Device> {
        let device_id = self.current_output_device_id.read().await;
        if let Some(id) = device_id.as_ref() {
            self.find_output_device_by_id(id).await.ok()
        } else {
            self.get_default_output_device()
        }
    }
}

impl Default for AudioDeviceManager {
    fn default() -> Self {
        Self::new().expect("Failed to create AudioDeviceManager")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tokio;

    #[tokio::test]
    async fn test_audio_device_manager_creation() {
        let manager = AudioDeviceManager::new();
        assert!(manager.is_ok());
    }

    #[tokio::test]
    async fn test_device_enumeration() {
        let manager = AudioDeviceManager::new().unwrap();
        let result = manager.enumerate_devices().await;
        assert!(result.is_ok());
        
        let input_devices = manager.get_input_devices().await;
        let output_devices = manager.get_output_devices().await;
        
        // Should have at least some devices (even if virtual)
        println!("Found {} input devices", input_devices.len());
        println!("Found {} output devices", output_devices.len());
        
        for device in &input_devices {
            println!("Input device: {} ({})", device.name, device.id);
        }
        
        for device in &output_devices {
            println!("Output device: {} ({})", device.name, device.id);
        }
    }

    #[tokio::test]
    async fn test_device_selection() {
        let manager = AudioDeviceManager::new().unwrap();
        manager.enumerate_devices().await.unwrap();
        
        let input_devices = manager.get_input_devices().await;
        let output_devices = manager.get_output_devices().await;
        
        if !input_devices.is_empty() {
            let device_id = &input_devices[0].id;
            let result = manager.set_input_device(device_id).await;
            assert!(result.is_ok());
            
            let current_id = manager.get_current_input_device_id().await;
            assert_eq!(current_id, Some(device_id.clone()));
        }
        
        if !output_devices.is_empty() {
            let device_id = &output_devices[0].id;
            let result = manager.set_output_device(device_id).await;
            assert!(result.is_ok());
            
            let current_id = manager.get_current_output_device_id().await;
            assert_eq!(current_id, Some(device_id.clone()));
        }
    }
}